using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.IO;
using System.Windows.Forms;

namespace WbsEditor
{
    class WbsSerializer
    {
        private WbsSerializer()
        {
        }

        #region Public static methods
        public static string SerializeToString(WorkBreakdownStructure wbs, bool createHtml)
        {
            try
            {
                WbsXml3 wbsXml = BuildWbsXml(wbs);
                XmlSerializer serializer = new XmlSerializer(typeof(WbsXml3));
                using (StringWriter stringWriter = new StringWriter())
                {
                    serializer.Serialize(stringWriter, wbsXml);
                    stringWriter.Flush();
                    if (!createHtml)
                        return stringWriter.ToString();
                    return Htmlify(stringWriter.ToString());
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("While serializing the XML, the following error occurred:\n\n" + ex.Message, "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }
        }


        public static string SerializeNodeToString(WbsNode node)
        {
            try
            {
                WbsXmlNode wbsXmlNode = BuildWbsXmlNode(node);
                wbsXmlNode.IsRoot = false;
                XmlSerializer serializer = new XmlSerializer(typeof(WbsXmlNode));
                using (StringWriter stringWriter = new StringWriter())
                {
                    serializer.Serialize(stringWriter, wbsXmlNode);
                    stringWriter.Flush();
                    return stringWriter.ToString();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("While serializing a WBS node to XML, the following error occurred:\n\n" + ex.Message, "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }
        }

        public static bool Serialize(WorkBreakdownStructure wbs, string xmlFileName)
        {
            try
            {
                WbsXml3 wbsXml = BuildWbsXml(wbs);
                XmlSerializer serializer = new XmlSerializer(typeof(WbsXml3));
                File.Delete(xmlFileName);
                using (FileStream fs = new FileStream(xmlFileName, FileMode.OpenOrCreate))
                {
                    serializer.Serialize(fs, wbsXml);
                }
                wbs.FileName = xmlFileName;

                // While Save, update the current, saved XML String
                wbs.XmlOriginal = SerializeToString(wbs, true);
                return true;
            }
            catch (Exception ex)
            {
                MessageBox.Show("While saving the XML file, an error occurred:\n\n" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            return false;
        }

        public static WorkBreakdownStructure Deserialize(string xmlFileName)
        {
            try
            {
                using (StreamReader fs = new StreamReader(xmlFileName))
                {
                    string xmlString = fs.ReadToEnd();
                    WorkBreakdownStructure wbs = DeserializeFromString(xmlString);
                    if (null != wbs)
                    {
                        // While Load, save unmodified XML String
                        wbs.XmlOriginal = SerializeToString(wbs, true);
                        wbs.FileName = xmlFileName;
                        return wbs;
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("While loading the XML, the following error occurred:\n\n" + ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            return null;
        }

        public static WorkBreakdownStructure DeserializeFromString(string xml)
        {
            try
            {
                WorkBreakdownStructure wbs = null;
                // Try new format first; then fallback to old format.
                // New Format: <WbsXml3> root node.
                bool wasDeHtmlified = false;
                try
                {
                    if (xml.StartsWith("<p>") || xml.Contains("<div class"))
                    {
                        xml = DeHtmlify(xml);
                        wasDeHtmlified = true;
                    }
                    StringReader stringReader = new StringReader(xml);
                    XmlSerializer serializer = new XmlSerializer(typeof(WbsXml3));
                    WbsXml3 wbsXml = (WbsXml3)serializer.Deserialize(stringReader);
                    if (!VerifyWbsEditorVersion(wbsXml))
                        return null;
                    wbs = BuildWbs3(wbsXml);
                }
                catch (Exception ex)
                {
                }

                // Was that successful? If not, also try old format <WbsXml> root node.
                if (null == wbs)
                {
                    StringReader stringReader = new StringReader(xml);
                    XmlSerializer serializer = new XmlSerializer(typeof(WbsXml));
                    WbsXml wbsXml = (WbsXml)serializer.Deserialize(stringReader);
                    wbs = BuildWbs(wbsXml);
                }

                wbs.FileName = "!!Workitem!!";
                wbs.LastSaveDate = DateTime.Now;

                return wbs;
            }
            catch (Exception ex)
            {
                MessageBox.Show("While loading the XML, the following error occurred:\n\n" + ex.Message, "Error",
                    MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }
        }

        private static string DeHtmlify(string htmlXml)
        {
            return htmlXml.Replace("<p>", "")
                .Replace("</p>", "")
                .Replace("<div class=Data>", "")
                .Replace("</div>", "")
                .Replace("<br>", "")
                .Replace("encoding=\"utf-16\"", "")
                .Replace("\n", "")
                .Replace("&quot;", "\"")
                .Replace("&lt;", "<")
                .Replace("&gt;", ">")
                .Replace("&amp;", "&")
                .Replace("&nbsp;", "&#160;")
                ;
        }

        private static string Htmlify(string xml)
        {
            string temp = xml
                .Replace("&", "&amp;")
                .Replace(">", "&gt;")
                .Replace("<", "&lt;")
                .Replace("\"", "&quot;");
            return string.Format("<p>{0}</p>", temp);
        }

        private static bool VerifyWbsEditorVersion(WbsXml3 wbsXml)
        {
            if (MainForm.VERSION.CompareTo(wbsXml.WbsEditorVersion) >= 0)
                return true;

            MessageBox.Show("Version conflict!\n\nWbsEditor Version: " + MainForm.VERSION + "\n" +
                "XML Version: " + wbsXml.WbsEditorVersion + "\n\n" +
                "Please download the latest version of WbsEditor.",
                "Information", MessageBoxButtons.OK, MessageBoxIcon.Warning);

            System.Diagnostics.Process.Start("http://wbseditor.codeplex.com");

            return false;
        }

        public static WbsNode DeserializeNodeFromString(string xmlString, WorkBreakdownStructure wbs)
        {
            try
            {
                StringReader stringReader = new StringReader(xmlString);
                XmlSerializer serializer = new XmlSerializer(typeof(WbsXmlNode));
                WbsXmlNode wbsXmlNode = (WbsXmlNode)serializer.Deserialize(stringReader);
                WbsNode node = BuildWbsNode(wbsXmlNode, wbs);
                return node;
            }
            catch (Exception ex)
            {
                MessageBox.Show("While deserializing a node from an XML string, the following error occurred\n\n" + ex.Message,
                    "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return null;
            }
        }
        #endregion

        #region Private methods
        private static WbsXml3 BuildWbsXml(WorkBreakdownStructure wbs)
        {
            WbsXml3 wbsXml = new WbsXml3();
            wbsXml.ConceptLink = wbs.ConceptLink;
            wbsXml.TID = wbs.TID;
            wbsXml.Title = wbs.Title;
            wbsXml.Owner = wbs.Owner;
            wbsXml.Root = BuildWbsXmlNode(wbs.Root);
            wbsXml.DisplayUnit = WorkBreakdownStructure.TimeUnitToString(wbs.DisplayUnit);
            wbsXml.PersistUnit = WorkBreakdownStructure.TimeUnitToString(wbs.PersistUnit);
            wbsXml.DaysToHoursFactor = wbs.DaysToHoursFactor > 0.004f ? wbs.DaysToHoursFactor : 8f;
            wbsXml.WbsEditorVersion = MainForm.VERSION;
            return wbsXml;
        }

        private static WorkBreakdownStructure BuildWbs(WbsXml wbsXml)
        {
            WorkBreakdownStructure wbs = new WorkBreakdownStructure();
            wbs.ConceptLink = wbsXml.ConceptLink;
            wbs.TID = wbsXml.TID;
            wbs.Title = wbsXml.Title;
            wbs.Owner = wbsXml.Owner;
            wbs.DisplayUnit = WorkBreakdownStructure.TimeUnit.Days;
            wbs.PersistUnit = WorkBreakdownStructure.TimeUnit.Days;
            wbs.DaysToHoursFactor = 8f;
            wbs.Root = BuildWbsNode(wbsXml.Root, wbs);
            return wbs;
        }

        private static WorkBreakdownStructure BuildWbs3(WbsXml3 wbsXml)
        {
            WorkBreakdownStructure wbs = new WorkBreakdownStructure();
            wbs.ConceptLink = wbsXml.ConceptLink;
            wbs.TID = wbsXml.TID;
            wbs.Title = wbsXml.Title;
            wbs.Owner = wbsXml.Owner;
            wbs.Root = BuildWbsNode(wbsXml.Root, wbs);
            wbs.DisplayUnit = WorkBreakdownStructure.StringToTimeUnit(wbsXml.DisplayUnit);
            wbs.PersistUnit = WorkBreakdownStructure.StringToTimeUnit(wbsXml.PersistUnit);
            wbs.DaysToHoursFactor = wbsXml.DaysToHoursFactor > 0.004 ? wbsXml.DaysToHoursFactor : 8f;

            //Due to a change these values have to be adjusted
            if (wbs.Root.CompletedWorkInDays == wbs.Root.InnerNodeCompletedWorkInDays)
                wbs.Root.CompletedWorkInDays = 0;
            if (wbs.Root.RemainingWorkInDays == wbs.Root.InnerNodeRemainingWorkInDays)
                wbs.Root.RemainingWorkInDays = 0;

            return wbs;
        }

        private static WbsXmlNode BuildWbsXmlNode(WbsNode node)
        {
            WbsXmlNode xmlNode = new WbsXmlNode();
            xmlNode.WorkItemID = node.WorkItemId;
            xmlNode.WorkItemType = node.WorkItemType;
            xmlNode.IsRoot = node.IsRoot;
            xmlNode.Title = node.Title;
            xmlNode.AssignedTo = node.AssignedTo;
            xmlNode.Iteration = node.Iteration;
            xmlNode.Priority = node.Priority;
            xmlNode.State = node.State;
            xmlNode.RemainingWorkInDays = node.RemainingWorkInDays;
            xmlNode.CompletedWorkInDays = node.CompletedWorkInDays;
            if (!node.IsRoot)
                xmlNode.Description = node.Description;
            else
                xmlNode.Description = "";
            xmlNode.PspCode = node.PspCode;
            xmlNode.IsSelected = false; // node.IsSelected;
            xmlNode.IsCollapsed = node.IsCollapsed;
            xmlNode.IsCollapsedSpecified = true;
            xmlNode.PlausiText = node.PlausiText == null ? "" : node.PlausiText;
            xmlNode.LayoutMode = (int)node.LayoutMode;
            xmlNode.LayoutModeSpecified = true;
            xmlNode.ChildNodes = new WbsXmlNode[node.ChildNodes.Count];
            for (int i = 0; i < node.ChildNodes.Count; ++i)
            {
                xmlNode.ChildNodes[i] = BuildWbsXmlNode(node.ChildNodes[i]);
            }
            return xmlNode;
        }

        private static WbsNode BuildWbsNode(WbsXmlNode xmlNode, WorkBreakdownStructure wbs)
        {
            string wiType = xmlNode.WorkItemType;
            if (string.IsNullOrEmpty(wiType))
                wiType = "Task";
            WbsNode node = new WbsNode(wiType);

            node.WorkItemId = xmlNode.WorkItemID;
            node.Wbs = wbs;

            node.IsRoot = xmlNode.IsRoot;
            node.Title = xmlNode.Title;
            node.AssignedTo = xmlNode.AssignedTo;
            node.Iteration = xmlNode.Iteration;
            node.Priority = xmlNode.Priority;
            node.State = xmlNode.State;
            node.RemainingWorkInDays = xmlNode.RemainingWorkInDays;
            node.CompletedWorkInDays = xmlNode.CompletedWorkInDays;
            if (!xmlNode.IsRoot)
                node.Description = xmlNode.Description;
            else
                node.Description = "";
            node.PspCode = xmlNode.PspCode;
            node.IsSelected = false; //xmlNode.IsSelected;
            if (xmlNode.IsCollapsedSpecified)
                node.IsCollapsed = xmlNode.IsCollapsed;
            else
                node.IsCollapsed = false;
            node.PlausiText = xmlNode.PlausiText == null ? "" : xmlNode.PlausiText;
            for (int i = 0; i < xmlNode.ChildNodes.Length; ++i)
            {
                node.AddChild(BuildWbsNode(xmlNode.ChildNodes[i], wbs));
            }
            if (xmlNode.LayoutModeSpecified)
                node.LayoutMode = (WbsNode.NodeLayout)xmlNode.LayoutMode;
            else
                node.LayoutMode = xmlNode.IsRoot ? WbsNode.NodeLayout.Horizontal : WbsNode.NodeLayout.Vertical;
            return node;
        }
        #endregion
    }
}
